/*******************************************************
 * @作者: zhaohua
 * @日期: 2011-11-8
 * @描述: 轨迹 路径 图层
 * @声明: copyrights reserved by Petfone 2007-2011
 *******************************************************/
package com.lolaage.tracks.adapter;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.CornerPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.Log;
import android.view.Gravity;
import android.widget.PopupWindow;

import com.baidu.mapapi.GeoPoint;
import com.baidu.mapapi.MapView;
import com.baidu.mapapi.Overlay;
import com.baidu.mapapi.Projection;
import com.lolaage.tracks.R;
import com.lolaage.tracks.util.LocationUtils;

/**
 * @author zhaohua
 *
 */
public class PathOverlay extends Overlay
{
	private static final String TAG = "PathOverlay";
	/** 画笔，可定义路线的样式*/
	private Paint paint;
	/** 路线*/
	private Path path;
	/** 最后一次地图原点记录*/
	private GeoPoint lastReferencePoint;
	/** 最后一次地图可见范围*/
	private Rect lastViewRect;
	/** 开始标志*/
	private final Drawable startMarker;
	/** 尾部标志*/
	private final Drawable endMarker;
	/** 尾部标志点范围*/
	private Rect  endMarkRect;
	/** 起始点坐标*/
	private GeoPoint startGp;
	private GeoPoint endGp;
	/** 路线是否总是可见，如2点在地图放大的情形下，可能都不可见，2点的连线可见性设置*/
	private boolean alwaysVisible = true;
	/** 路线的可见性*/
	private boolean pathVisible = false;
	/** 标志的宽与高*/
	private final int markerWidth, markerHeight;
	/** 绘制用 坐标列表*/
	private final List<CachedLocation> points;
	/** 添加用坐标队列*/
	private final BlockingQueue<CachedLocation> pendingPoints;
	/** 结束标志 弹出窗*/
	private PopupWindow endPopWindow;
	
	public PathOverlay(Context context)
	{
		this.points = new ArrayList<CachedLocation>(1024);
		this.pendingPoints = new ArrayBlockingQueue<CachedLocation>(1000,true);
		
		final Resources resources = context.getResources();
		
		startMarker = resources.getDrawable(R.drawable.track_start_marker);
		markerWidth = startMarker.getIntrinsicWidth();
		markerHeight = startMarker.getIntrinsicHeight();
		startMarker.setBounds(0, 0, markerWidth, markerHeight);

	    endMarker = resources.getDrawable(R.drawable.track_end_marker);
	    endMarker.setBounds(0, 0, markerWidth, markerHeight);
	    
		paint = new Paint();
		paint.setARGB(255, 255, 0, 0);
		paint.setStrokeWidth(4);
		paint.setStyle(Paint.Style.STROKE);
		paint.setAntiAlias(true);
		paint.setDither(true);
		paint.setPathEffect(new CornerPathEffect(2));
		
		endMarkRect = new Rect();
		path = new Path();
	}

	/** 获取起点位置*/
	public GeoPoint getStartGP()
	{
		return startGp;
	}
	/** 获取始点位置*/
    public GeoPoint getEndGP()
    {
        return endGp;
    }
	
	/** 添加一个坐标*/
	public void addLocation(GeoPoint point)
	{				
		if (!pendingPoints.offer(new CachedLocation(point)))
		{
			Log.e(TAG, "Unable to add pending points");
		}
	}
	/** 添加一个碎片坐标,当获取的不合法的坐标时*/
	public void addSegmentSplit()
	{
		if (!pendingPoints.offer(new CachedLocation()))
		{
			Log.e(TAG, "Unable to add pending points");
		}
	}
	/** 清楚坐标路线*/
	public void clearPoints()
	{
		synchronized (points)
		{
			points.clear();
			pendingPoints.clear();
			path.reset();
			pathVisible = false;
		}
	}
	/** 设置OnTap的pop win*/
	public void setEndPopWin(PopupWindow endPop)
	{
		endPopWindow = endPop;
	}
	/** 开始 与 结束 标志的点击事件*/
	@Override
	public boolean onTap(GeoPoint p, final MapView mapView)
	{
		final Projection projection = mapView.getProjection();
		if (projection == null)
		{
			Log.w(TAG, "No projection, unable to draw");
			return false;
		}
		
		Point pt = new Point();
		projection.toPixels(p, pt);
		
		if(endMarkRect.contains(pt.x,pt.y))
		{
			if(endPopWindow != null)
			{
				endPopWindow.showAtLocation(mapView, Gravity.LEFT|Gravity.TOP, pt.x, pt.y);
			}
			return true;
		}
		
		return false;
	}
	/** 绘制图层*/
	@Override
	public void draw(Canvas canvas, MapView mapView, boolean shadow)
	{
		super.draw(canvas, mapView, shadow);
		if (shadow)
		{
			return;
		}

		// It's safe to keep projection within a single draw operation.
		final Projection projection = mapView.getProjection();
		if (projection == null)
		{
			Log.w(TAG, "No projection, unable to draw");
			return;
		}
		Rect viewRect = LocationUtils.getMapViewRect(mapView);        
		// Draw the selected track:
		drawTrack(canvas, projection, viewRect);
		// Draw the "Start" and "End" markers:
        drawMarkers(canvas, projection);
	}
	/** 绘制轨迹路线*/
	private void drawTrack(Canvas canvas, Projection projection, Rect viewRect)
	{
		boolean draw;

		synchronized (points)
		{
			// Merge the pending points with the list of cached locations.
			// 左上角地图原点 point
			final GeoPoint referencePoint = projection.fromPixels(0, 0);
			int newPoints = pendingPoints.drainTo(points);
			// 地图是否移动
			boolean mapRectChanged = !viewRect.equals(lastViewRect);
			// 地图原点是否改变
			boolean mapPointChanged = !referencePoint.equals(lastReferencePoint);
			// 是否是新的投影
			boolean newProjection = mapRectChanged || mapPointChanged;
			// 没有任何新的节点 而且 投影没有改变，就重绘缓存path
			if (newPoints == 0 && !newProjection && pathVisible)
			{
				// No need to recreate path (same points and viewing area).
				draw = true;
				Log.d(TAG, "--->no new points, refush view!");
			}
			else
			{
				int numPoints = points.size();
				// 少于2个不足以构成path
				if (numPoints < 2)
				{
					// Not enough points to draw a path.
					draw = false;
					Log.d(TAG, "--->less than 2 points, no draw path!");
				}
				// 当投影没有 发生改变时，就新增 path
				else if (!newProjection && pathVisible)
				{
					// Incremental update of the path, without repositioning the view.
					draw = true;
					updatePath(projection, viewRect, numPoints - newPoints, points);
					pathVisible = true;
					Log.d(TAG, "--->append more points, append update path!");
				}
				// 投影改变时，就重头更新 path
				else
				{
					// The view has changed so we have to start from scratch.
					draw = true;
					updatePath(projection, viewRect, 0, points);
					pathVisible = true;
					Log.d(TAG, "--->projection changed,reset update path!");
				}
			}
			// 记录最后一次的投影参数
			lastReferencePoint = referencePoint;
			lastViewRect = viewRect;
		}
		if (draw)
		{
			canvas.drawPath(path, paint);
		}
	}
	/** 绘制开始与结束标志*/
	private void drawMarkers(Canvas canvas, Projection projection)
	{
		// Draw the "End" marker.
	    int size = points.size();
	    if(size > 1)
	    {
    		for (int i =  size - 1; i >= 0; --i)
    		{
    			if (points.get(i).valid)
    			{
    			    endGp = points.get(i).geoPoint;
    				drawElement(canvas, projection, endGp, endMarker, -markerWidth / 2, -markerHeight, endMarkRect);
    				break;
    			}
    		}
		}

		// Draw the "Start" marker.
	    if(size > 0)
	    {
    		for (int i = 0; i < size; ++i)
    		{
    			if (points.get(i).valid)
    			{
    				startGp = points.get(i).geoPoint;
    				drawElement(canvas, projection, startGp , startMarker, -markerWidth / 2, -markerHeight);
    				break;
    			}
    		}
	    }
	}
	/** 绘制图片元素，并保存该元素的有效点击范围*/
	private Point drawElement(Canvas canvas, Projection projection, GeoPoint geoPoint,
			Drawable element, int offsetX, int offsetY, Rect rect)
	{
		Point pt = new Point();
		projection.toPixels(geoPoint, pt);
		canvas.save();
		canvas.translate(pt.x + offsetX, pt.y + offsetY);
		element.draw(canvas);
		canvas.restore();
		
		if(rect != null)
		{
			rect.left = pt.x + offsetX;
			rect.top =  pt.y + offsetY;
			rect.right = pt.x - offsetX;
			rect.bottom = pt.y;
		}
		return pt;
	}

	private Point drawElement(Canvas canvas, Projection projection, GeoPoint geoPoint,
			Drawable element, int offsetX, int offsetY)
	{
		return drawElement(canvas,projection,geoPoint,element,offsetX,offsetY,null);
	}
	
	/** 更新 path*/
	private void updatePath(Projection projection, Rect viewRect,
			int startLocationIdx, List<CachedLocation> points)
	{
		// Whether to start a new segment on new valid and visible point.
		boolean newSegment = startLocationIdx <= 0 || !points.get(startLocationIdx - 1).valid;
		boolean lastVisible = !newSegment;
		final Point pt = new Point();
		// Loop over track points.
		int numPoints = points.size();
		if (startLocationIdx == 0)
		{
			// 重头
			path.reset();
			path.incReserve(numPoints);
		}
		else
		{
			// 新增
			path.incReserve(numPoints - startLocationIdx);
		}

		for (int i = startLocationIdx; i < numPoints; ++i)
		{
			CachedLocation loc = points.get(i);
			// Check if valid, if not then indicate a new segment.
			if (!loc.valid)
			{
				newSegment = true;
				continue;
			}

			final GeoPoint geoPoint = loc.geoPoint;
			// Check if this breaks the existing segment.
			boolean visible = viewRect.contains(geoPoint.getLongitudeE6(), geoPoint.getLatitudeE6());
			// 当该节点不可见，且上一个节点也不见时，定为新的碎片
			// 如果设置为总是可见，则不是碎片
			if ( !visible && !lastVisible && !alwaysVisible)
			{
				// This is a point outside view not connected to a visible one.
				newSegment = true;
			}
			lastVisible = visible;
			// Either move to beginning of a new segment or continue the old one.
			projection.toPixels(geoPoint, pt);
			// set path
			if (newSegment)
			{
				// 如果该节点时碎片，就移动到该点
				path.moveTo(pt.x, pt.y);
				newSegment = false;
			}
			else
			{
				// 不是，则连接到该点
				path.lineTo(pt.x, pt.y);
			}
		}
	}
	
	/** 坐标包装*/
	public static class CachedLocation
	{
		public final boolean valid;
		public final GeoPoint geoPoint;

		/**
		 * Constructor for an invalid cached location.
		 */
		public CachedLocation()
		{
			this.valid = false;
			this.geoPoint = null;
		}

		/**
		 * Constructor for a potentially valid cached location.
		 */
		public CachedLocation(GeoPoint point)
		{
			this.valid = LocationUtils.isValidGeoPoint(point);
			this.geoPoint = valid ? point : null;
		}
	};
}
